﻿using IronLua.Metadata;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace IronLua
{
    internal static class Builtin
    {
        internal const int BinaryChunkMode = 0x1b;

        public static class Prototypes
        {
            public static readonly Prototype load = Prototype.FromClrFunction("load", Builtin.load);
            public static readonly Prototype print = Prototype.FromClrFunction("print", Builtin.print);
        }

        public static class Functions
        {
            public static readonly Value load = Value.FromFunction(new Closure(Prototypes.load));
            public static readonly Value print = Value.FromFunction(new Closure(Prototypes.print));
        }

        /// <summary>
        /// load (ld [, source [, mode [, env]]])
        /// </summary>
        public static int load(Thread thread)
        {
            var ld = thread.Stack.TryPopParameterOrDefault(Value.Nil);

            if (ld.IsNil)
            {
                throw new LuaRuntimeException("expected string or function, got nil.");
            }

            string source = "", mode = "";

            if (!thread.Stack.TryPopParameterOrDefault(ld.IsString ? ld : Value.FromString("(=load)")).TryGetLiteralString(out source))
            {
                throw new LuaRuntimeException("source must be a string.");
            }

            if (!thread.Stack.TryPopParameterOrDefault(Value.FromString("bt")).TryGetLiteralString(out mode))
            {
                throw new LuaRuntimeException("mode must be a string.");
            }

            var env = thread.Stack.TryPopParameterOrDefault(Value.FromAny(thread.Environ.Globals));

            byte[] bytes;

            if (ld.IsFunction)
            {
                throw new NotImplementedException();
            }
            else if (ld.IsString)
            {
                bytes = Encoding.ASCII.GetBytes(ld.String);
            }
            else if (ld.IsLightUserData)
            {
                bytes = (byte[])ld.ClrObject;
            }
            else
            {
                throw new LuaRuntimeException("expected string or function, got {0}.".FormatWith(ld.GetTypeString()));
            }

            if (bytes == null)
            {
                throw new InvalidOperationException("nothing to load.");
            }
            
            var allowBinary = mode.Contains('b');
            var allowText = mode.Contains('t');

            var isBinary = bytes.Length >= 1 ? bytes[0] == Builtin.BinaryChunkMode : false;
            var isText = !isBinary;

            Prototype proto;
            using (var stream = new MemoryStream(bytes))
            {
                if (isBinary && allowBinary)
                {
                    var chunk = stream.ReadLuaChunk();
                    proto = chunk.TopFunction;
                    var closure = new Closure(proto, new[] { env });
                    thread.Stack.Push(Value.FromFunction(closure));
                    return 1;
                }
                else if (isText && allowText)
                {
                    throw new NotImplementedException();
                }
            }

            thread.Stack.Push(Value.False);
            thread.Stack.Push(Value.FromString("invalid chunk mode, {0} expected.".FormatWith(mode)));
            return 2;
        }

        /// <summary>
        /// print(...)
        /// </summary>
        public static int print(Thread thread)
        {

            return 0;
        }

    }
}
